﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Core.IO;
using Core.Util;
using System.Drawing;

namespace GPSCrowdSource.DataStructures {
  
  /// <summary>
  /// A lines direction.
  /// </summary>
  public enum eRTDDirection {
    N,
    S,
    E,
    W
    } 

  /// <summary>
  /// The lines that are available.
  /// </summary>
  public enum eRTDLine {
    C,
    D,
    E,
    F,
    H,
    W,
    Unknown
    }

  /// <summary>
  /// Manages the RTD database for stuff.
  /// </summary>
  public class RTDDatabase {

    #region Private

    private static Object ourMutex = new Object();
    private static RTDDatabase ourInst = null;

    /// <summary>
    /// Map of the line to the arrival, and departure time.s
    /// </summary>
    private Dictionary<eRTDLine,Route> myTimeMap = new Dictionary<eRTDLine,Route>();

    #endregion

    #region Private Utils

    /// <summary>
    /// From the current position parse until a line contains the given text and
    /// return all the text inbetween excluding the line and comments and blank lines.
    /// </summary>
    /// <returns>The line that contains the msg.</returns>
    private String ParseUntil(String msg, TextReader inRead) {
      String ln = null;
      StringBuilder sb = new StringBuilder();

      // Move to header
      while((ln = inRead.ReadLine()) != null) {
        ln = ln.Trim();
        if(ln.Length == 0 || ln[0] == '#') continue;
        if(ln.Contains(msg)) break;
        sb.Append(ln + Environment.NewLine);
        }

      return sb.ToString();
      } 
    
    /// <summary>
    /// Parse the file header and return a route graph that should be populated.
    /// Creates a route if one does not exist or returns one
    /// </summary>
    private Route ParseHeaderAndGetRoute(String filePath, TextReader inReader,out eRTDDirection dir) {
      
      String locs = null;
      String stations = null;
      
      // Get the line and direction.
      String nm = Path.GetFileNameWithoutExtension(filePath);
      String[] typeStuff = nm.Split(new char[] {'_'},StringSplitOptions.RemoveEmptyEntries);
      eRTDLine line = (eRTDLine)Enum.Parse(typeof(eRTDLine),typeStuff[0]);
      dir = (eRTDDirection)Enum.Parse(typeof(eRTDDirection),typeStuff[1]);
      
      Route route = Query(line);
      if(route == null) {
        route = new Route(line);
        myTimeMap.Add(line,route);
        }

      ParseUntil("S_HDR",inReader);
      
      String hdr = ParseUntil("E_HDR",inReader);

      if(hdr == null) throw new ArgumentException("There is a formatting issue with the header of the file\r\n" + stations + "\r\n" + locs);
      
      foreach(String ln in hdr.Split(new String[] {Environment.NewLine},StringSplitOptions.RemoveEmptyEntries)) {
        String[] hdrLine = ln.Split(new char[] {','},StringSplitOptions.RemoveEmptyEntries);
        
        Station station = new Station(line,dir,hdrLine[0]);
        String[] ll = hdrLine[1].Split(new char[] {'/'},StringSplitOptions.RemoveEmptyEntries);
        station.Latitude = float.Parse(ll[0]);
        station.Longitude = float.Parse(ll[1]);

        String[] clr = hdrLine[2].Split(new char[] {'-'},StringSplitOptions.RemoveEmptyEntries);
        station.Color = Color.FromArgb(int.Parse(clr[0]),
                       int.Parse(clr[1]),
                       int.Parse(clr[2]));
        route.Add(station);

        }

      return route;
      }

    /// <summary>
    /// Load the given route file.
    /// </summary>
    private Route Load(String file) {

      try {

        String data = File.ReadAllText(file);
        String ln;
        StringReader sr = new StringReader(data);
        eRTDDirection dir;

        // Parse the header
        Route r = ParseHeaderAndGetRoute(file,sr,out dir);
        int row = 0;

        while((ln = sr.ReadLine()) != null) {
          ln = ln.Trim();
          if(ln.Length == 0 || ln[0] == '#') continue;

          // Format is "Rail, time1, time2, ... timen
          String[] spl = ln.Split(new char[] {' '},StringSplitOptions.RemoveEmptyEntries);
          
          // Load all the departure times.
          for(int i=1;i<spl.Length-1;i++) {
            String tm = spl[i];

            if(tm.Trim().Contains('-'))  {
              r[i-1].Add(dir,new TimeSpan(0,0,0));
              }
            else if(tm.Length == 4) {
              int hr = int.Parse(tm.Substring(0,1));
              int min = int.Parse(tm.Substring(1,2));
              String pa = tm.Substring(3,1);
              if(pa == "P" && hr != 12) hr += 11;
              r[i-1].Add(dir,new TimeSpan(hr,min,0));
              }
            else if(tm.Length == 5) {
              int hr = int.Parse(tm.Substring(0,2));
              int min = int.Parse(tm.Substring(2,2));
              String pa = tm.Substring(4,1);
              if(pa == "P" && hr != 12) hr += 11;
              r[i-1].Add(dir,new TimeSpan(hr,min,0));
              }

            }

          row++;

          }
          
        return r;
        }
      catch(Exception ex) { Log.Error(ex); }

      return null;
      }

    #endregion

    #region Constructor

    public RTDDatabase() {
      try {
        String fp = GeoConstants.RAIL_DB;

        foreach(String f in Directory.GetFiles(fp)) {
          if(File.Exists(f) && f.ToLower().Contains("_line")) 
            Load(f);
          } 

        }
      catch(Exception ex) {
        Log.Error(ex);
        }
      }

    #endregion

    /// <summary>
    /// Get the DB instance.
    /// </summary>
    public static RTDDatabase Instance {

      get {

        lock(ourMutex) {
          if(ourInst == null) ourInst =new RTDDatabase();
          return ourInst;
          }
        }
      }

    /// <summary>
    /// Ask for the given route, or null if it does not exist in the DB.
    /// </summary>
    public Route Query(eRTDLine inLine) {
      lock(myTimeMap) {
        if(myTimeMap.ContainsKey(inLine))
          return myTimeMap[inLine];
        return null;
        }
      }

    }

  }
